<?php

namespace System\Core;

/**
 * Description of Input
 *
 * @author elton.schivei
 */
class Input extends \System\Types\Object
{

    public $ip_address = false;
    public $user_agent = false;
    private $_allow_get_array = true;
    private $_standardize_newlines = true;
    private $_enable_xss = false; // Set automatically based on config setting
    private $_enable_csrf = false; // Set automatically based on config setting
    protected $headers = array();

    /**
     * @var \System\Core\UTF8
     */
    private $uni;

    /**
     * @var \System\Core\Security
     */
    private $security;

    /**
     * @var \System\Core\Input
     */
    private static $_instance;

    /**
     * @return \System\Core\Input
     */
    public static function &GetInstance()
    {
        if (!self::$_instance)
            self::$_instance = new self();

        return self::$_instance;
    }

    function __construct()
    {
        parent::__construct();

        System\Lib\Log::Write("Input Class Initialized", System\Lib\Log::DEBUG);

        $_ = Config::GetValue(Config::GENERAL, 'allow_get_array');
        $_1 = Config::GetValue(Config::GENERAL, 'global_xss_filtering');
        $_2 = Config::GetValue(Config::GENERAL, 'csrf_protection');

        $this->_allow_get_array = ($_ === true);
        $this->_enable_xss = ($_1 === true);
        $this->_enable_csrf = ($_2 === true);

        $this->security = \System\Core\Security::GetInstance();

        // Do we need the UTF-8 class?
        if (UTF8::Utf8Enabled() === true)
        {
            $this->uni = UTF8::GetInstance();
        }

        // Sanitize global arrays
        $this->_sanitize_globals();
    }

    // --------------------------------------------------------------------

    /**
     * Fetch from array
     *
     * This is a helper function to retrieve values from global arrays
     *
     * @access	private
     * @param	array
     * @param	string
     * @param	bool
     * @return	string
     */
    private function _fetch_from_array(&$array, $index = '', $xss_clean = false)
    {
        if (!isset($array[$index]))
        {
            return false;
        }

        if ($xss_clean === true)
        {
            return $this->security->XssClean($array[$index]);
        }

        return $array[$index];
    }

    // --------------------------------------------------------------------

    /**
     * Fetch an item from the GET array
     *
     * @access	public
     * @param	string
     * @param	bool
     * @return	string
     */
    public function Get($index = null, $xss_clean = false)
    {
        // Check if a field has been provided
        if ($index === null && !empty($_GET))
        {
            $get = array();

            // loop through the full _GET array
            foreach (\array_keys($_GET) as $key)
            {
                $get[$key] = $this->_fetch_from_array($_GET, $key, $xss_clean);
            }
            return $get;
        }

        return $this->_fetch_from_array($_GET, $index, $xss_clean);
    }

    // --------------------------------------------------------------------

    /**
     * Fetch an item from the POST array
     *
     * @access	public
     * @param	string
     * @param	bool
     * @return	string
     */
    public function Post($index = null, $xss_clean = false)
    {
        // Check if a field has been provided
        if ($index === null && !empty($_POST))
        {
            $post = array();

            // Loop through the full _POST array and return it
            foreach (\array_keys($_POST) as $key)
            {
                $post[$key] = $this->_fetch_from_array($_POST, $key, $xss_clean);
            }
            return $post;
        }

        return $this->_fetch_from_array($_POST, $index, $xss_clean);
    }

    // --------------------------------------------------------------------

    /**
     * Fetch an item from either the GET array or the POST
     *
     * @access	public
     * @param	string	The index key
     * @param	bool	XSS cleaning
     * @return	string
     */
    public function GetPost($index = '', $xss_clean = false)
    {
        if (!isset($_POST[$index]))
        {
            return $this->Get($index, $xss_clean);
        }
        else
        {
            return $this->Post($index, $xss_clean);
        }
    }

    // --------------------------------------------------------------------

    /**
     * Fetch an item from the COOKIE array
     *
     * @access	public
     * @param	string
     * @param	bool
     * @return	string
     */
    public function Cookie($index = '', $xss_clean = false)
    {
        return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
    }

    // ------------------------------------------------------------------------

    /**
     * Set cookie
     *
     * Accepts six parameter, or you can submit an associative
     * array in the first parameter containing all the values.
     *
     * @access	public
     * @param	mixed
     * @param	string	the value of the cookie
     * @param	string	the number of seconds until expiration
     * @param	string	the cookie domain.  Usually:  .yourdomain.com
     * @param	string	the cookie path
     * @param	string	the cookie prefix
     * @param	bool	true makes the cookie secure
     * @return	void
     */
    public function SetCookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = false)
    {
        if (\is_array($name))
        {
            // always leave 'name' in last place, as the loop will break otherwise, due to $$item
            foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'name') as $item)
            {
                if (isset($name[$item]))
                {
                    $$item = $name[$item];
                }
            }
        }

        if ($prefix == '' && \config_item('cookie_prefix') != '')
        {
            $prefix = \config_item('cookie_prefix');
        }
        if ($domain == '' && \config_item('cookie_domain') != '')
        {
            $domain = \config_item('cookie_domain');
        }
        if ($path == '/' && \config_item('cookie_path') != '/')
        {
            $path = \config_item('cookie_path');
        }
        if ($secure == false && \config_item('cookie_secure') != false)
        {
            $secure = \config_item('cookie_secure');
        }

        if (!\is_numeric($expire))
        {
            $expire = \time() - 86500;
        }
        else
        {
            $expire = ($expire > 0) ? \time() + $expire : 0;
        }

        \setcookie($prefix . $name, $value, $expire, $path, $domain, $secure);
    }

    // --------------------------------------------------------------------

    /**
     * Fetch an item from the SERVER array
     *
     * @access	public
     * @param	string
     * @param	bool
     * @return	string
     */
    public function Server($index = '', $xss_clean = false)
    {
        return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
    }

    // --------------------------------------------------------------------

    /**
     * Fetch the IP Address
     *
     * @access	public
     * @return	string
     */
    public function IpAddress()
    {
        if ($this->ip_address !== false)
        {
            return $this->ip_address;
        }

        if (\config_item('proxy_ips') != '' && $this->Server('HTTP_X_FORWARDED_FOR') && $this->Server('REMOTE_ADDR'))
        {
            $proxies = \preg_split('/[\s,]/', \config_item('proxy_ips'), -1, \PREG_SPLIT_NO_EMPTY);
            $proxies = \is_array($proxies) ? $proxies : array($proxies);

            $this->ip_address = \in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
        }
        elseif ($this->Server('REMOTE_ADDR') && $this->Server('HTTP_CLIENT_IP'))
        {
            $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
        }
        elseif ($this->Server('REMOTE_ADDR'))
        {
            $this->ip_address = $_SERVER['REMOTE_ADDR'];
        }
        elseif ($this->Server('HTTP_CLIENT_IP'))
        {
            $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
        }
        elseif ($this->Server('HTTP_X_FORWARDED_FOR'))
        {
            $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
        }

        if ($this->ip_address === false)
        {
            $this->ip_address = '0.0.0.0';
            return $this->ip_address;
        }

        if (\strpos($this->ip_address, ',') !== false)
        {
            $x = \explode(',', $this->ip_address);
            $this->ip_address = \trim(\end($x));
        }

        if (!$this->ValidIp($this->ip_address))
        {
            $this->ip_address = '0.0.0.0';
        }

        return $this->ip_address;
    }

    // --------------------------------------------------------------------

    /**
     * Validate IP Address
     *
     * Updated version suggested by Geert De Deckere
     *
     * @access	public
     * @param	string
     * @return	string
     */
    public function ValidIp($ip)
    {
        $ip_segments = \explode('.', $ip);

        // Always 4 segments needed
        if (\count($ip_segments) != 4)
        {
            return false;
        }
        // IP can not start with 0
        if ($ip_segments[0][0] == '0')
        {
            return false;
        }
        // Check each segment
        foreach ($ip_segments as $segment)
        {
            // IP segments must be digits and can not be
            // longer than 3 digits or greater then 255
            if ($segment == '' || \preg_match("/[^0-9]/", $segment) || $segment > 255 || \strlen($segment) > 3)
            {
                return false;
            }
        }

        return true;
    }

    // --------------------------------------------------------------------

    /**
     * User Agent
     *
     * @access	public
     * @return	string
     */
    public function UserAgent()
    {
        if ($this->user_agent !== false)
        {
            return $this->user_agent;
        }

        $this->user_agent = (!isset($_SERVER['HTTP_USER_AGENT'])) ? false : $_SERVER['HTTP_USER_AGENT'];

        return $this->user_agent;
    }

    // --------------------------------------------------------------------

    /**
     * Sanitize Globals
     *
     * This function does the following:
     *
     * Unsets $_GET data (if query strings are not enabled)
     *
     * Unsets all globals if register_globals is enabled
     *
     * Standardizes newline characters to \n
     *
     * @access	private
     * @return	void
     */
    private function _sanitize_globals()
    {
        // It would be "wrong" to unset any of these GLOBALS.
        $protected = array('_SERVER', '_GET', '_POST', '_FILES', '_REQUEST',
            '_SESSION', '_ENV', 'GLOBALS', 'HTTP_RAW_POST_DATA',
            'system_folder', 'application_folder', 'BM', 'EXT',
            'CFG', 'URI', 'RTR', 'OUT', 'IN');

        // Is $_GET data allowed? If not we'll set the $_GET to an empty array
        if ($this->_allow_get_array == false)
        {
            $_GET = array();
        }
        else
        {
            if (\is_array($_GET) && \count($_GET) > 0)
            {
                foreach ($_GET as $key => $val)
                {
                    $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
                }
            }
        }

        // Clean $_POST Data
        if (\is_array($_POST) && \count($_POST) > 0)
        {
            foreach ($_POST as $key => $val)
            {
                $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
            }
        }

        // Clean $_COOKIE Data
        if (\is_array($_COOKIE) && \count($_COOKIE) > 0)
        {
            // Also get rid of specially treated cookies that might be set by a server
            // or silly application, that are of no use to a CI application anyway
            // but that when present will trip our 'Disallowed Key Characters' alarm
            // http://www.ietf.org/rfc/rfc2109.txt
            // note that the key names below are single quoted strings, and are not PHP variables
            unset($_COOKIE['$Version']);
            unset($_COOKIE['$Path']);
            unset($_COOKIE['$Domain']);

            foreach ($_COOKIE as $key => $val)
            {
                $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
            }
        }

        // Sanitize PHP_SELF
        $_SERVER['PHP_SELF'] = \strip_tags($_SERVER['PHP_SELF']);


        // CSRF Protection check
        if ($this->_enable_csrf == true)
        {
            $this->security->CsrfVerify();
        }

        \System\Lib\Log::Write("Global POST and COOKIE data sanitized", \System\Lib\Log::DEBUG);
    }

    // --------------------------------------------------------------------

    /**
     * Clean Input Data
     *
     * This is a helper function. It escapes data and
     * standardizes newline characters to \n
     *
     * @access	private
     * @param	string
     * @return	string
     */
    private function _clean_input_data($str)
    {
        if (\is_array($str))
        {
            $new_array = array();
            foreach ($str as $key => $val)
            {
                $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
            }
            return $new_array;
        }

        // Clean UTF-8 if supported
        if ($this->uni)
        {
            $str = $this->uni->CleanString($str);
        }

        // Remove control characters
        $str = \System\Types\String::RemoveInvisibleCharacters($str);

        // Should we filter the input data?
        if ($this->_enable_xss === true)
        {
            $str = $this->security->XssClean($str);
        }

        // Standardize newlines if needed
        if ($this->_standardize_newlines == true)
        {
            if (\strpos($str, "\r") !== false)
            {
                $str = \str_replace(array("\r\n", "\r", "\r\n\n"), \PHP_EOL, $str);
            }
        }

        return $str;
    }

    // --------------------------------------------------------------------

    /**
     * Clean Keys
     *
     * This is a helper function. To prevent malicious users
     * from trying to exploit keys we make sure that keys are
     * only named with alpha-numeric text and a few other items.
     *
     * @access	private
     * @param	string
     * @return	string
     */
    private function _clean_input_keys($str)
    {
        if (!\preg_match("/^[a-z0-9:_\/-]+$/i", $str))
        {
            exit('Disallowed Key Characters.');
        }

        // Clean UTF-8 if supported
        if ($this->uni)
        {
            $str = $this->uni->CleanString($str);
        }

        return $str;
    }

    // --------------------------------------------------------------------

    /**
     * Request Headers
     *
     * In Apache, you can simply call apache_request_headers(), however for
     * people running other webservers the function is undefined.
     *
     * @return array
     */
    public function RequestHeaders($xss_clean = false)
    {
        // Look at Apache go!
        if (\function_exists('apache_request_headers'))
        {
            $headers = \apache_request_headers();
        }
        else
        {
            $headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @\getenv('CONTENT_TYPE');

            foreach ($_SERVER as $key => $val)
            {
                if (\strncmp($key, 'HTTP_', 5) === 0)
                {
                    $headers[\substr($key, 5)] = $this->_fetch_from_array($_SERVER, $key, $xss_clean);
                }
            }
        }

        // take SOME_HEADER and turn it into Some-Header
        foreach ($headers as $key => $val)
        {
            $key = \str_replace('_', ' ', \strtolower($key));
            $key = \str_replace(' ', '-', \ucwords($key));

            $this->headers[$key] = $val;
        }

        return $this->headers;
    }

    // --------------------------------------------------------------------

    /**
     * Get Request Header
     *
     * Returns the value of a single member of the headers class member
     *
     * @param 	string		array key for $this->headers
     * @param	boolean		XSS Clean or not
     * @return 	mixed		false on failure, string on success
     */
    public function GetRequestHeader($index, $xss_clean = false)
    {
        if (empty($this->headers))
        {
            $this->RequestHeaders();
        }

        if (!isset($this->headers[$index]))
        {
            return false;
        }

        if ($xss_clean === true)
        {
            return $this->security->XssClean($this->headers[$index]);
        }

        return $this->headers[$index];
    }

    // --------------------------------------------------------------------

    /**
     * Is ajax Request?
     *
     * Test to see if a request contains the HTTP_X_REQUESTED_WITH header
     *
     * @return 	boolean
     */
    public function IsAjaxRequest()
    {
        return ($this->Server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest');
    }

    // --------------------------------------------------------------------

    /**
     * Is cli Request?
     *
     * Test to see if a request was made from the command line
     *
     * @return 	boolean
     */
    public function IsCliRequest()
    {
        return (bool) \defined('\STDIN');
    }

}
